import {
  generateDataSourceFetcher,
  generateDataSourceFetcherWithCore,
  getDataSourceDependencies,
} from '../src/data-source-fetchers'
import { validateDatabaseConfig } from '../src/validation'
import {
  validateMongoDBConfig,
  validateRedisConfig,
  validateRESTAPIConfig,
  validateJavaScriptConfig,
  validateStaticCollectionConfig,
} from '../src/fetchers'
import {
  createPostgreSQLDataSource,
  createMySQLDataSource,
  createMongoDBDataSource,
  createRESTAPIDataSource,
  createJavaScriptDataSource,
  createStaticCollectionDataSource,
  createRedisDataSource,
} from './mocks'

describe('getDataSourceDependencies', () => {
  it('returns correct dependencies for REST API', () => {
    const deps = getDataSourceDependencies('rest-api')
    expect(deps.packages).toContain('node-fetch')
    expect(deps.isDefaultImport).toBe(true)
  })

  it('returns correct dependencies for PostgreSQL', () => {
    const deps = getDataSourceDependencies('postgresql')
    expect(deps.packages).toContain('pg')
    expect(deps.isDefaultImport).toBe(false)
  })

  it('returns correct dependencies for MySQL', () => {
    const deps = getDataSourceDependencies('mysql')
    expect(deps.packages).toContain('mysql2')
  })

  it('returns correct dependencies for MongoDB', () => {
    const deps = getDataSourceDependencies('mongodb')
    expect(deps.packages).toContain('mongodb')
  })

  it('returns correct dependencies for Redis', () => {
    const deps = getDataSourceDependencies('redis')
    expect(deps.packages).toContain('redis')
  })

  it('returns empty dependencies for embedded sources', () => {
    expect(getDataSourceDependencies('javascript').packages).toEqual([])
    expect(getDataSourceDependencies('csv-file').packages).toEqual([])
    expect(getDataSourceDependencies('static-collection').packages).toEqual([])
  })

  it('handles all supported data source types', () => {
    const types = [
      'rest-api',
      'postgresql',
      'mysql',
      'mariadb',
      'amazon-redshift',
      'mongodb',
      'cockroachdb',
      'tidb',
      'redis',
      'firestore',
      'clickhouse',
      'airtable',
      'supabase',
      'turso',
      'javascript',
      'google-sheets',
      'csv-file',
      'static-collection',
    ]

    types.forEach((type) => {
      const deps = getDataSourceDependencies(type as any)
      expect(deps).toBeDefined()
      expect(Array.isArray(deps.packages)).toBe(true)
    })
  })
})

describe('validateDatabaseConfig', () => {
  it('validates correct database config', () => {
    const result = validateDatabaseConfig({
      host: 'localhost',
      port: 5432,
      user: 'testuser',
      password: 'testpass',
      database: 'testdb',
    })
    expect(result.isValid).toBe(true)
  })

  it('accepts username instead of user', () => {
    const result = validateDatabaseConfig({
      host: 'localhost',
      username: 'testuser',
      password: 'testpass',
      database: 'testdb',
    })
    expect(result.isValid).toBe(true)
  })

  it('rejects missing host', () => {
    const result = validateDatabaseConfig({
      port: 5432,
      user: 'testuser',
      password: 'testpass',
      database: 'testdb',
    })
    expect(result.isValid).toBe(false)
    expect(result.error).toContain('Host')
  })

  it('rejects invalid port', () => {
    const result = validateDatabaseConfig({
      host: 'localhost',
      port: 99999,
      user: 'testuser',
      password: 'testpass',
      database: 'testdb',
    })
    expect(result.isValid).toBe(false)
    expect(result.error).toContain('Port')
  })

  it('rejects missing password', () => {
    const result = validateDatabaseConfig({
      host: 'localhost',
      user: 'testuser',
      database: 'testdb',
    })
    expect(result.isValid).toBe(false)
    expect(result.error).toContain('Password')
  })

  it('rejects missing database', () => {
    const result = validateDatabaseConfig({
      host: 'localhost',
      user: 'testuser',
      password: 'testpass',
    })
    expect(result.isValid).toBe(false)
    expect(result.error).toContain('Database')
  })

  it('allows optional port', () => {
    const result = validateDatabaseConfig({
      host: 'localhost',
      user: 'testuser',
      password: 'testpass',
      database: 'testdb',
    })
    expect(result.isValid).toBe(true)
  })
})

describe('validateMongoDBConfig', () => {
  it('validates with connection string', () => {
    const result = validateMongoDBConfig({
      connectionString: 'mongodb://localhost:27017/testdb',
      database: 'testdb',
    })
    expect(result.isValid).toBe(true)
  })

  it('validates mongodb+srv protocol', () => {
    const result = validateMongoDBConfig({
      connectionString: 'mongodb+srv://cluster.mongodb.net/testdb',
      database: 'testdb',
    })
    expect(result.isValid).toBe(true)
  })

  it('validates with host/port', () => {
    const result = validateMongoDBConfig({
      host: 'localhost',
      port: 27017,
      database: 'testdb',
    })
    expect(result.isValid).toBe(true)
  })

  it('rejects invalid connection string', () => {
    const result = validateMongoDBConfig({
      connectionString: 'invalid://connection',
      database: 'testdb',
    })
    expect(result.isValid).toBe(false)
  })

  it('requires database field even with connection string', () => {
    const result = validateMongoDBConfig({
      connectionString: 'mongodb://localhost:27017/testdb',
      database: 'testdb',
    })
    expect(result.isValid).toBe(true)
  })

  it('accepts secret references in connection string', () => {
    const result = validateMongoDBConfig({
      connectionString: 'teleporthq.secrets.MONGO_CONNECTION',
      database: 'testdb',
    })
    expect(result.isValid).toBe(true)
  })
})

describe('validateRedisConfig', () => {
  it('validates with host', () => {
    const result = validateRedisConfig({
      host: 'localhost',
    })
    expect(result.isValid).toBe(true)
  })

  it('validates with connection string', () => {
    const result = validateRedisConfig({
      connectionString: 'redis://localhost:6379',
    })
    expect(result.isValid).toBe(true)
  })

  it('validates with rediss protocol', () => {
    const result = validateRedisConfig({
      connectionString: 'rediss://localhost:6379',
    })
    expect(result.isValid).toBe(true)
  })

  it('validates with secret reference', () => {
    const result = validateRedisConfig({
      connectionString: 'teleporthq.secrets.REDIS_CONNECTION',
    })
    expect(result.isValid).toBe(true)
  })

  it('rejects invalid connection string', () => {
    const result = validateRedisConfig({
      connectionString: 'invalid://localhost',
    })
    expect(result.isValid).toBe(false)
  })

  it('requires either host or connectionString', () => {
    const result = validateRedisConfig({
      port: 6379,
    })
    expect(result.isValid).toBe(false)
  })
})

describe('validateRESTAPIConfig', () => {
  it('validates correct REST API config', () => {
    const result = validateRESTAPIConfig({
      url: 'https://api.example.com/data',
      method: 'GET',
    })
    expect(result.isValid).toBe(true)
  })

  it('accepts all valid HTTP methods', () => {
    const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
    methods.forEach((method) => {
      const result = validateRESTAPIConfig({
        url: 'https://api.example.com',
        method,
      })
      expect(result.isValid).toBe(true)
    })
  })

  it('rejects invalid URL', () => {
    const result = validateRESTAPIConfig({
      url: 'not-a-url',
    })
    expect(result.isValid).toBe(false)
  })

  it('rejects non-HTTP protocols', () => {
    const result = validateRESTAPIConfig({
      url: 'ftp://example.com',
    })
    expect(result.isValid).toBe(false)
  })

  it('rejects invalid HTTP method', () => {
    const result = validateRESTAPIConfig({
      url: 'https://api.example.com',
      method: 'INVALID',
    })
    expect(result.isValid).toBe(false)
  })
})

describe('validateJavaScriptConfig', () => {
  it('validates correct JavaScript config', () => {
    const result = validateJavaScriptConfig({
      code: '[{id: 1, name: "Item 1"}]',
    })
    expect(result.isValid).toBe(true)
  })

  it('rejects missing code', () => {
    const result = validateJavaScriptConfig({})
    expect(result.isValid).toBe(false)
    expect(result.error).toContain('code')
  })

  it('rejects empty code', () => {
    const result = validateJavaScriptConfig({
      code: '',
    })
    expect(result.isValid).toBe(false)
  })

  it('warns about dangerous patterns but still validates', () => {
    const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()

    const result = validateJavaScriptConfig({
      code: 'require("fs")',
    })

    expect(result.isValid).toBe(true)
    expect(consoleSpy).toHaveBeenCalled()

    consoleSpy.mockRestore()
  })
})

describe('validateStaticCollectionConfig', () => {
  it('validates correct static collection', () => {
    const result = validateStaticCollectionConfig({
      data: [{ id: 1 }, { id: 2 }],
    })
    expect(result.isValid).toBe(true)
  })

  it('rejects non-array data', () => {
    const result = validateStaticCollectionConfig({
      data: 'not an array',
    })
    expect(result.isValid).toBe(false)
  })

  it('rejects missing data', () => {
    const result = validateStaticCollectionConfig({})
    expect(result.isValid).toBe(false)
  })

  it('accepts empty array', () => {
    const result = validateStaticCollectionConfig({
      data: [],
    })
    expect(result.isValid).toBe(true)
  })
})

describe('generateDataSourceFetcher', () => {
  it('generates PostgreSQL fetcher', () => {
    const dataSource = createPostgreSQLDataSource('ds-1')
    const code = generateDataSourceFetcher(dataSource, 'users')

    expect(code).toContain('import { Pool } from')
    expect(code).toContain('export default async function handler')
    expect(code).toContain('SELECT * FROM users')
    expect(code).toContain('localhost')
  })

  it('generates MySQL fetcher', () => {
    const dataSource = createMySQLDataSource('ds-1')
    const code = generateDataSourceFetcher(dataSource, 'products')

    expect(code).toContain('mysql')
    expect(code).toContain('products')
    expect(code).toContain('FROM')
  })

  it('generates MongoDB fetcher', () => {
    const dataSource = createMongoDBDataSource('ds-1')
    const code = generateDataSourceFetcher(dataSource, 'orders')

    expect(code).toContain('MongoClient')
    expect(code).toContain("collection('orders')")
  })

  it('generates REST API fetcher', () => {
    const dataSource = createRESTAPIDataSource('ds-1')
    const code = generateDataSourceFetcher(dataSource, '')

    expect(code).toContain('fetch')
    expect(code).toContain('https://api.example.com/data')
  })

  it('generates JavaScript fetcher', () => {
    const dataSource = createJavaScriptDataSource('ds-1')
    const code = generateDataSourceFetcher(dataSource, '')

    expect(code).toContain('executeCode')
    expect(code).toContain('new Function')
  })

  it('generates Static Collection fetcher', () => {
    const dataSource = createStaticCollectionDataSource('ds-1')
    const code = generateDataSourceFetcher(dataSource, '')

    expect(code).toContain('const data = ')
    expect(code).toContain('"Item 1"')
    expect(code).toContain('"Item 2"')
  })

  it('generates Redis fetcher', () => {
    const dataSource = createRedisDataSource('ds-1')
    const code = generateDataSourceFetcher(dataSource, '')

    expect(code).toContain('redis')
    expect(code).toContain('createClient')
  })

  it('handles CockroachDB as PostgreSQL', () => {
    const dataSource = {
      id: 'ds-1',
      name: 'CockroachDB',
      type: 'cockroachdb' as const,
      config: {
        host: 'localhost',
        port: 26257,
        user: 'root',
        password: 'password',
        database: 'testdb',
      },
    }
    const code = generateDataSourceFetcher(dataSource, 'users')

    expect(code).toContain('Pool')
  })

  it('handles TiDB as MySQL', () => {
    const dataSource = {
      id: 'ds-1',
      name: 'TiDB',
      type: 'tidb' as const,
      config: {
        host: 'localhost',
        port: 4000,
        user: 'root',
        password: 'password',
        database: 'testdb',
      },
    }
    const code = generateDataSourceFetcher(dataSource, 'users')

    expect(code).toContain('mysql')
  })

  it('throws error for invalid data source', () => {
    expect(() => {
      generateDataSourceFetcher(null as any, 'users')
    }).toThrow('Invalid data source')
  })

  it('throws error for missing type', () => {
    expect(() => {
      generateDataSourceFetcher({ config: {} } as any, 'users')
    }).toThrow('type is required')
  })

  it('throws error for missing config', () => {
    expect(() => {
      generateDataSourceFetcher({ type: 'postgresql' } as any, 'users')
    }).toThrow('config is required')
  })

  it('throws error for unsupported type', () => {
    expect(() => {
      generateDataSourceFetcher({ type: 'unsupported', config: {} } as any, 'users')
    }).toThrow('Unsupported data source type')
  })

  it('handles validation errors', () => {
    expect(() => {
      generateDataSourceFetcher(
        {
          type: 'postgresql',
          config: { host: '' },
        } as any,
        'users'
      )
    }).toThrow('validation failed')
  })
})

describe('generateDataSourceFetcherWithCore', () => {
  it('generates both fetchData and handler functions', () => {
    const dataSource = createPostgreSQLDataSource('ds-1')
    const code = generateDataSourceFetcherWithCore(dataSource, 'users')

    expect(code).toContain('async function fetchData')
    expect(code).toContain('async function handler')
    expect(code).toContain('export { fetchData, fetchCount, handler, getCount }')
    expect(code).toContain('export default { fetchData, fetchCount, handler, getCount }')
  })

  it('includes simulation of req/res for fetchData', () => {
    const dataSource = createPostgreSQLDataSource('ds-1')
    const code = generateDataSourceFetcherWithCore(dataSource, 'users')

    expect(code).toContain('const req = {')
    expect(code).toContain('query: params')
    expect(code).toContain('const res = {')
    expect(code).toContain('status: (code)')
    expect(code).toContain('json: (data)')
  })

  it('includes error handling in fetchData', () => {
    const dataSource = createPostgreSQLDataSource('ds-1')
    const code = generateDataSourceFetcherWithCore(dataSource, 'users')

    expect(code).toContain('if (statusCode !== 200')
    expect(code).toContain('throw new Error')
  })

  it('works with all data source types', () => {
    const dataSources = [
      createPostgreSQLDataSource('ds-1'),
      createMySQLDataSource('ds-2'),
      createMongoDBDataSource('ds-3'),
      createRESTAPIDataSource('ds-4'),
      createJavaScriptDataSource('ds-5'),
      createStaticCollectionDataSource('ds-6'),
    ]

    dataSources.forEach((dataSource) => {
      const code = generateDataSourceFetcherWithCore(dataSource, 'test')
      expect(code).toContain('async function fetchData')
      expect(code).toContain('async function handler')
    })
  })
})
